#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: com.hpp
//      Author: Ziming Li
//     Created: 2023-11-09
// -------------------------------------------------
// Copyright (C) 2023 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_WIN_COM_HPP
#define ZED_WIN_COM_HPP

#include <functional>
#include <Windows.h>
#include "../memory.hpp"

namespace zed {

template <class T, typename = std::enable_if_t<std::is_base_of_v<IUnknown, T>>>
class com_ptr : public ptr_base<T>
{
public:
    com_ptr(void) : ptr_base<T>(nullptr) { }
    constexpr com_ptr(nullptr_t) : ptr_base<T>(nullptr) { }
    explicit com_ptr(T* p) : ptr_base<T>(p) { }
    com_ptr(const com_ptr& o);
    ~com_ptr(void);

    T** operator&();
    com_ptr& operator=(const com_ptr& o);

    void release(void);

    template <class U, typename = std::enable_if_t<std::is_base_of_v<IUnknown, U>>>
    com_ptr<U> to(void) const;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations

template <class T, typename E>
com_ptr<T, E>::com_ptr(const com_ptr& o) : ptr_base<T>(o.m_ptr)
{
    if (nullptr != this->m_ptr)
        this->m_ptr->AddRef();
}

template <class T, typename E>
com_ptr<T, E>::~com_ptr(void)
{
    if (nullptr != this->m_ptr)
        this->m_ptr->Release();
}

template <class T, typename E>
T** com_ptr<T, E>::operator&()
{
    ZASSERT(nullptr == this->m_ptr);
    return &this->m_ptr;
}

template <class T, typename E>
com_ptr<T, E>& com_ptr<T, E>::operator=(const com_ptr& o)
{
    if (this->m_ptr != o.m_ptr)
    {
        if (nullptr != this->m_ptr)
            this->m_ptr->Release();
        this->m_ptr = o.m_ptr;
        if (nullptr != this->m_ptr)
            this->m_ptr->AddRef();
    }
    return *this;
}

template <class T, typename E>
void com_ptr<T, E>::release(void)
{
    if (nullptr != this->m_ptr)
    {
        this->m_ptr->Release();
        this->m_ptr = nullptr;
    }
}

template <class T, typename E>
template <class U, typename>
com_ptr<U> com_ptr<T, E>::to(void) const
{
    com_ptr<U> ret;
    if (SUCCEEDED(this->m_ptr->QueryInterface(__uuidof(U), reinterpret_cast<void**>(&ret))))
    {
        ZASSERT(ret);
        return ret;
    }
    return nullptr;
}

} // namespace zed

#endif // ZED_WIN_COM_HPP
